/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package operator.server;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import share.MessageType;
import share.Server;
import share.SocketCommunicator;

/**
 *
 * @author mire
 */
public class OperatorCentralThread implements Runnable {

    private Socket clientSocket;
    private SocketCommunicator socComm;
    private OperatorServer operatorServer;
    private final SimpleDateFormat dateFormat;

    public OperatorCentralThread(Socket clientSocket, OperatorServer operatorServer) {
        this.clientSocket = clientSocket;
        this.operatorServer = operatorServer;
        this.dateFormat = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy");
    }

    @Override
    public void run() {

        String status = null;
        MessageType msg;
        boolean error = false;
        int userIDError = 0;
        ArrayList<Integer> userIDsError = null;

        String threadInfo = dateFormat.format(new Date());
        threadInfo += " " + this.hashCode() + " ";
        try {
            this.socComm = new SocketCommunicator(clientSocket, operatorServer.getWaitForAnswerTime());
            socComm.open();
            String request = socComm.readString();

            threadInfo += "central " + operatorServer.getCentralAddress() + " " + request + " ";
            System.out.println(threadInfo);


            try {
                msg = MessageType.valueOf(request);
            } catch (IllegalArgumentException ex) {
                status = "ERROR_NO_REQUEST";
                System.out.println(threadInfo + status);
                socComm.writeString(status);
                socComm.close();
                return;
            }

            switch (msg) {
                case ALIVE: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    socComm.writeString("YES");
                    socComm.close();
                    if (!operatorServer.isCentralServerOnline()) {
                        setCentralServerAlive();
                    }
                    break;
                }
                //</editor-fold>

                case REMOVE_USER: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    String newOperatorName;
                    int userID;
                    String userNumber;
                    Date synchronizedTime;

                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    userID = socComm.readInt();
                    userNumber = socComm.readString();
                    newOperatorName = socComm.readString();

                    OperatorUser user = operatorServer.getUsers().get(userID);
                    if (!user.getNumber().equals(userNumber)) {
                        status = "ERROR_ID_NUMBER_MISMATCH";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }

                    if (user.getCurrentOperator().equals(newOperatorName)) {
                        status = "USER_REMOVED";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }

                    status = "REMOVING_USER";
                    System.out.println(threadInfo + status);

                    user.setCurrentOperator(newOperatorName);
                    synchronizedTime = new Date();
                    operatorServer.setLastTimeSynchronized(synchronizedTime);
                    //backup
                    operatorServer.backupUsers();
                    //sync
                    syncRemoveUser(userID, newOperatorName, synchronizedTime);

                    status = "USER_REMOVED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    userIDError = userID;
                    status = socComm.readString();
                    System.out.println(threadInfo + status);
                    if (!status.equals("USER_TRANSFERED")) {
                        error = true;
                    }

                    socComm.close();
                    break;
                }
                //</editor-fold>

                case REMOVE_MULTIPLE_USERS://<editor-fold defaultstate="collapsed" desc="...">
                {
                    String newOperatorName;
                    String userList;
                    Date synchronizedTime;

                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    newOperatorName = socComm.readString();
                    userList = socComm.readString();

                    ArrayList<Integer> userIDs = operatorServer.parseUserIDs(userList);
                    for (Iterator<Integer> it = userIDs.iterator(); it.hasNext();) {
                        operatorServer.getUsers().get(it.next()).setCurrentOperator(newOperatorName);

                    }
                    status = "REMOVING_MULTIPLE_USERS";
                    System.out.println(threadInfo + status);

                    synchronizedTime = new Date();
                    operatorServer.setLastTimeSynchronized(synchronizedTime);
                    //backup
                    operatorServer.backupUsers();
                    //sync with one server
                    syncRemoveMultipleUsers(userIDs, newOperatorName, synchronizedTime);

                    status = "MULTIPLE_USERS_REMOVED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    userIDsError = userIDs;
                    status = socComm.readString();
                    System.out.println(threadInfo + status);
                    if (!status.equals("USER_TRANSFERED")) {
                        error = true;
                    }

                    socComm.close();
                    break;

                }
                //</editor-fold>

                case TRANSFER: //<editor-fold defaultstate="collapsed" desc="...">
                {

                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    int userID = socComm.readInt();
                    String userNumber = socComm.readString();
                    String oldOperatorName = socComm.readString();

                    //dodaj kod sebe
                    status = "ADDING_USER";
                    System.out.println(threadInfo + status);
                    Date synchronizedTime = new Date();
                    OperatorUser newUser = new OperatorUser(userID, userNumber, oldOperatorName, operatorServer.getOperatorName());
                    operatorServer.getUsers().put(newUser.getId(), newUser);
                    operatorServer.setLastTimeSynchronized(synchronizedTime);
                    //sinhronizuj se sa ostalim serverima
                    status = "SYNCING_WITH_OTHERS";
                    System.out.println(threadInfo + status);
                    syncTransferUserWithOthers(newUser, synchronizedTime);
                    //snimanje transfera
                    status = "LOGGING_TRANSFERS";
                    System.out.println(threadInfo + status);
                    operatorServer.getTransferLog().logTransfer(synchronizedTime, userID, userNumber, oldOperatorName);                    //zavrsheno
                    operatorServer.backupUsers();
                    operatorServer.backupConfiguration();
                    status = "USER_TRANSFERED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    break;
                }
                //</editor-fold>

                case MULTIPLE_TRANSFER: //<editor-fold defaultstate="collapsed" desc="...">
                {

                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    String oldOperatorName = socComm.readString();
                    String usersString = socComm.readString();

                    //dodaj kod sebe
                    status = "ADDING_MULTIPLE_USERS";
                    System.out.println(threadInfo + status);
                    Date synchronizedTime = new Date();
                    ConcurrentHashMap<Integer, OperatorUser> newUsers = operatorServer.parseUsers(usersString, oldOperatorName);

                    operatorServer.getUsers().putAll(newUsers);
                    operatorServer.setLastTimeSynchronized(synchronizedTime);
                    //sinhronizuj se sa ostalim serverima
                    status = "SYNCING_WITH_OTHERS";
                    System.out.println(threadInfo + status);
                    syncTransferMultipleUsersWithOthers(newUsers, synchronizedTime);
                    //snimanje transfera
                    status = "LOGGING_TRANSFERS";
                    System.out.println(threadInfo + status);
                    operatorServer.getTransferLog().logMultipleTransfers(synchronizedTime, newUsers, oldOperatorName);
                    operatorServer.backupUsers();
                    operatorServer.backupConfiguration();
                    //zavrsheno
                    status = "MULTIPLE_USERS_TRANSFERED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    break;
                }
                //</editor-fold>

                case UPDATE_OPERATOR://<editor-fold defaultstate="collapsed" desc="...">
                {
                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    if (operatorServer.getCentralDownUsers().isEmpty()) {
                        status = "NO_NEW_USERS";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                    } else {
                        status = "SENDING";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.writeString(operatorServer.getCentralDownUsers().toString());
                    }

                    socComm.writeObject(operatorServer.getServers());

                    status = socComm.readString();
                    System.out.println(threadInfo + status);
                    socComm.close();
//obavestiti ostale servere (meh)
                    status = "UPDATING_CENTRAL_SERVER_STATUS";
                    System.out.println(threadInfo + status);
                    setCentralServerAlive();
                    break;
                }
                //</editor-fold>
            }

        } catch (SocketException ex) {
            Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
            error = true;
        } catch (IOException ex) {
            Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
            error = true;
        } catch (OutOfMemoryError err) {
            Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, err);
            error = true;
            try {
                socComm.writeString("ERROR_OOM");
            } catch (IOException ex) {
            }
        } finally {
            try {
                socComm.close();
            } catch (IOException ex) {
                Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
            }
            if (error) {
                if (userIDError == 0) {
                    revertIfRemoveUser(userIDError);
                } else if (userIDsError != null) {
                    revertIfRemoveMultipleUsers(userIDsError);
                }
            }
            status = "FINISHED";
            System.out.println(threadInfo + status);
        }
    }

    private void syncRemoveUser(int userID, String newOperatorName, Date synchronizedTime) {
        SocketCommunicator sc = null;
        Iterator<Server> server = operatorServer.getServers().values().iterator();
        while (server.hasNext()) {
            String IPAddress = server.next().getAddress();
            if (IPAddress.equals(operatorServer.getAddress())) {
                continue;
            }

            try {
                sc = new SocketCommunicator(new Socket(IPAddress, operatorServer.getOperatorPort()));
                sc.open();

                sc.writeString("REMOVE_USER");
                sc.writeInt(userID);
                sc.writeString(newOperatorName);
                sc.writeObject(synchronizedTime);

                sc.readString();
                sc.close();


            } catch (UnknownHostException ex) {
                Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    sc.close();


                } catch (IOException ex) {
                    Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private void syncRemoveMultipleUsers(ArrayList<Integer> userIDs, String newOperatorName, Date synchronizedTime) {
        SocketCommunicator sc = null;
        for (Iterator<Server> it = operatorServer.getServers().values().iterator(); it.hasNext();) {
            {
                String IPAddress = it.next().getAddress();

                try {
                    sc = new SocketCommunicator(new Socket(IPAddress, operatorServer.getOperatorPort()));
                    sc.open();

                    sc.writeString("REMOVE_MULTIPLE_USERS");
                    sc.writeObject(userIDs);
                    sc.writeString(newOperatorName);
                    sc.writeObject(synchronizedTime);

                    sc.readString();
                    sc.close();

                } catch (UnknownHostException ex) {
                    Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    try {
                        sc.close();
                    } catch (IOException ex) {
                        Logger.getLogger(OperatorCentralThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
    }

    private void setCentralServerAlive() {
        operatorServer.getUsers().putAll(operatorServer.getCentralDownUsers());
        operatorServer.getCentralDownUsers().clear();
        operatorServer.backupUsers();
        operatorServer.setCentralServerOnline(true);
    }

    private void revertIfRemoveUser(int userID) {
        operatorServer.getUsers().get(userID).setCurrentOperator(operatorServer.getOperatorName());

        syncRemoveUser(userID, operatorServer.getOperatorName(), new Date());

    }

    private void revertIfRemoveMultipleUsers(ArrayList<Integer> userIDs) {
        ConcurrentHashMap<Integer, OperatorUser> users = operatorServer.getUsers();
        for (Iterator<Integer> it = userIDs.iterator(); it.hasNext();) {
            Integer userID = it.next();
            users.get(userID).setCurrentOperator(operatorServer.getOperatorName());
        }

        syncRemoveMultipleUsers(userIDs, operatorServer.getOperatorName(), new Date());
    }

    private boolean syncTransferUserWithOne(OperatorUser newUser, Date synchronizedTime) {
        boolean notified = false;
        for (Iterator<Server> it = operatorServer.getServers().values().iterator(); it.hasNext() && !notified;) {
            try {
                Server server = it.next();
                if (server.getAddress().equals(operatorServer.getAddress())) {
                    continue;
                }

                SocketCommunicator sco = new SocketCommunicator(new Socket(server.getAddress(), operatorServer.getOperatorPort()));
                try {
                    sco.open();
                    sco.writeString("TRANSFER");
                    sco.readString(); //REQUEST_ACCEPTED

                    sco.writeObject(newUser);
                    sco.writeObject(synchronizedTime);

                    sco.readString();
                    sco.close();
                    notified = true;
                } catch (IOException ex) {
                    Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    try {
                        sco.close();
                    } catch (IOException ex) {
                        Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            } catch (UnknownHostException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return notified;
    }

    private void syncTransferUserWithOthers(OperatorUser newUser, Date synchronizedTime) {

        for (Iterator<Server> it = operatorServer.getServers().values().iterator(); it.hasNext();) {
            try {
                Server server = it.next();
                if (server.getAddress().equals(operatorServer.getAddress())) {
                    continue;
                }

                SocketCommunicator sco = new SocketCommunicator(new Socket(server.getAddress(), operatorServer.getOperatorPort()));
                try {
                    sco.open();
                    sco.writeString("TRANSFER");
                    sco.readString(); //REQUEST_ACCEPTED

                    sco.writeObject(newUser);
                    sco.writeObject(synchronizedTime);

                    sco.readString();
                    sco.close();
                } catch (IOException ex) {
                    Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    try {
                        sco.close();
                    } catch (IOException ex) {
                        Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            } catch (UnknownHostException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private boolean syncTransferMultipleUsersWithOne(ConcurrentHashMap<Integer, OperatorUser> newUsers, Date synchronizedTime) {
        boolean notified = false;
        for (Iterator<Server> it = operatorServer.getServers().values().iterator(); it.hasNext() && !notified;) {
            try {
                Server server = it.next();
                if (server.getAddress().equals(operatorServer.getAddress())) {
                    continue;
                }

                SocketCommunicator sco = new SocketCommunicator(new Socket(server.getAddress(), operatorServer.getOperatorPort()));
                try {
                    sco.open();
                    sco.writeString("MULTIPLE_TRANSFER");
                    sco.readString();

                    sco.writeObject(newUsers);
                    sco.writeObject(synchronizedTime);

                    sco.readString();
                    sco.close();
                    notified = true;
                } catch (IOException ex) {
                    Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    try {
                        sco.close();
                    } catch (IOException ex) {
                        Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            } catch (UnknownHostException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return notified;
    }

    private void syncTransferMultipleUsersWithOthers(ConcurrentHashMap<Integer, OperatorUser> newUsers, Date synchronizedTime) {
        for (Iterator<Server> it = operatorServer.getServers().values().iterator(); it.hasNext();) {
            try {
                Server server = it.next();
                if (server.getAddress().equals(operatorServer.getAddress())) {
                    continue;
                }

                SocketCommunicator sco = new SocketCommunicator(new Socket(server.getAddress(), operatorServer.getOperatorPort()));
                try {
                    sco.open();
                    sco.writeString("MULTIPLE_TRANSFER");
                    sco.readString();

                    sco.writeObject(newUsers);
                    sco.writeObject(synchronizedTime);

                    sco.readString();
                    sco.close();
                } catch (IOException ex) {
                    Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    try {
                        sco.close();
                    } catch (IOException ex) {
                        Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            } catch (UnknownHostException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(OperatorClientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
